package gui.treeutil;

import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.event.TreeModelListener;
import javax.swing.table.TableModel;
import javax.swing.tree.AbstractLayoutCache;
import javax.swing.tree.FixedHeightLayoutCache;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.VariableHeightLayoutCache;

import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.OutlineModel;
import org.netbeans.swing.outline.RowModel;
import org.netbeans.swing.outline.TreePathSupport;

public class TransactionOutlineModel implements OutlineModel {
   private TreeModel treeModel;
   private TableModel tableModel;
   private AbstractLayoutCache layout;
   private TreePathSupport treePathSupport;
   private TransactionEventBroadcaster broadcaster;
   private String nodesColumnLabel = "Nodes";
   //Some constants we use to have a single method handle all translated
   //event firing
   private static final int NODES_CHANGED = 0;
   private static final int NODES_INSERTED = 1;
   private static final int NODES_REMOVED = 2;
   private static final int STRUCTURE_CHANGED = 3;
   
   /** Create a small model OutlineModel using the supplied tree model and row model 
    * @param treeModel The tree model that is the data model for the expandable
    *  tree column of an Outline
    * @param rowModel The row model which will supply values for each row based
    *  on the tree node in that row in the tree model
    */
   public static OutlineModel createOutlineModel(TreeModel treeModel, RowModel rowModel) {
       return createOutlineModel (treeModel, rowModel, false, null);
   }

   /** Create an OutlineModel using the supplied tree model and row model,
    * specifying if it is a large-model tree */
   public static OutlineModel createOutlineModel(TreeModel treeModel, RowModel rowModel, boolean isLargeModel) {
       return createOutlineModel (treeModel, rowModel, isLargeModel, null);
   }
   /** Create an OutlineModel using the supplied tree model and row model,
    * specifying if it is a large-model tree */
   public static OutlineModel createOutlineModel(TreeModel treeModel, RowModel rowModel, boolean isLargeModel, String nodesColumnLabel) {
       return new TransactionOutlineModel (treeModel, rowModel, isLargeModel, nodesColumnLabel);
   }
   
   protected TransactionOutlineModel(TreeModel treeModel, RowModel rowModel, boolean largeModel, String nodesColumnLabel) {
       this( treeModel, new ProxyTableModel(rowModel), largeModel, nodesColumnLabel );
   }
   
   /** Creates a new instance of DefaultOutlineModel.  <strong><b>Note</b> 
    * Do not fire table structure changes from the wrapped TableModel (value
    * changes are okay).  Changes that affect the number of rows must come
    * from the TreeModel.   */
   protected TransactionOutlineModel(TreeModel treeModel, TableModel tableModel, boolean largeModel, String nodesColumnLabel) {
       this.treeModel = treeModel;
       this.tableModel = tableModel;
       if (nodesColumnLabel != null) {
           this.nodesColumnLabel = nodesColumnLabel;
       }
       
       layout = largeModel ? (AbstractLayoutCache) new FixedHeightLayoutCache() 
           : (AbstractLayoutCache) new VariableHeightLayoutCache();
           
       broadcaster = new TransactionEventBroadcaster(this);
       
       layout.setRootVisible(true);
       layout.setModel(this);
       treePathSupport = new TreePathSupport(this, layout);
       treePathSupport.addTreeExpansionListener(broadcaster);
       treePathSupport.addTreeWillExpandListener(broadcaster);
       treeModel.addTreeModelListener(broadcaster);
       tableModel.addTableModelListener(broadcaster);
       if (tableModel instanceof ProxyTableModel) {
           ((ProxyTableModel) tableModel).setOutlineModel(this);
       }
   }
   
   public final TreePathSupport getTreePathSupport() {
       return treePathSupport;
   }    
   
   public final AbstractLayoutCache getLayout() {
       return layout;
   }
   
   public boolean areMoreEventsPending() {
       return broadcaster.areMoreEventsPending();
   }
   
   /** Accessor for EventBroadcaster */
   TreeModel getTreeModel() {
       return treeModel;
   }
   
   /** Accessor for EventBroadcaster */
   TableModel getTableModel() {
       return tableModel;
   }
   
   public final Object getChild(Object parent, int index) {
       return treeModel.getChild (parent, index);
   }
   
   public final int getChildCount(Object parent) {
       return treeModel.getChildCount (parent);
   }
   
   /** Delegates to the RowMapper for > 0 columns; column 0 always
    * returns Object.class */
   public final Class getColumnClass(int columnIndex) {
       if (columnIndex == 0) {
           return Object.class;
       } else {
           return tableModel.getColumnClass(columnIndex-1);
       }
   }
   
   public final int getColumnCount() {
       return tableModel.getColumnCount()+1;
   }
   
   public String getColumnName(int columnIndex) {
       if (columnIndex == 0) {
           return nodesColumnLabel;
       } else {
           return tableModel.getColumnName(columnIndex-1);
       }
   }
   
   /**
    * Change the label of the 'tree' column.
    * @param label New label for tree column.
    */
   public void setNodesColumnLabel( String label ) {
       this.nodesColumnLabel = label;
       broadcaster.fireTableChange( new TableModelEvent( this, -1, -1, 0, TableModelEvent.HEADER_ROW ) );
   }
   
   public final int getIndexOfChild(Object parent, Object child) {
       return treeModel.getIndexOfChild(parent, child);
   }
   
   public final Object getRoot() {
       return treeModel.getRoot();
   }
   
   public final int getRowCount() {
       return layout.getRowCount();
   }
   
   public final Object getValueAt(int rowIndex, int columnIndex) {
       Object result;
       if (columnIndex == 0) { //XXX need a column ID - columnIndex = 0 depends on the column model
       TreePath path = getLayout().getPathForRow(rowIndex);
       if (path != null) {
           result = path.getLastPathComponent();
           } else {
               result = null;
       }
       } else {
           result = (tableModel.getValueAt(rowIndex, columnIndex -1));
       }
       return result;
   }
    
   public boolean isCellEditable(int rowIndex, int columnIndex) {
       if (columnIndex == 0) {
           return false; //XXX support editing of node names
       } else {
           return tableModel.isCellEditable(rowIndex, columnIndex-1);
       }
   }
   
   public final boolean isLeaf(Object node) {
       return null != node && treeModel.isLeaf(node);
   }

   /** Delegates to the EventBroadcaster for this model */
   public final synchronized void addTableModelListener(TableModelListener l) {
       broadcaster.addTableModelListener (l);
   }
   
   /** Delegates to the EventBroadcaster for this model */
   public final synchronized void addTreeModelListener(TreeModelListener l) {
       broadcaster.addTreeModelListener (l);
   }    
   
   /** Delegates to the EventBroadcaster for this model */
   public final synchronized void removeTableModelListener(TableModelListener l) {
       broadcaster.removeTableModelListener(l);
   }
   
   /** Delegates to the EventBroadcaster for this model */
   public final synchronized void removeTreeModelListener(TreeModelListener l) {
       broadcaster.removeTreeModelListener(l);
   }
   
   /** Delegates to the RowModel (or TableModel) for non-0 columns */
   public final void setValueAt(Object aValue, int rowIndex, int columnIndex) {
       if (columnIndex != 0) {
           tableModel.setValueAt (aValue, rowIndex, columnIndex-1);
       } else {
           setTreeValueAt(aValue, rowIndex);
       }
   }
   
   /**
    * Sets the value of a 'tree' cell at given row number. 
    * The default implementation does nothing.
    * 
    * @param aValue
    * @param rowIndex
    */
   protected void setTreeValueAt(Object aValue, int rowIndex) {
       //do nothing
   }
   
   public final void valueForPathChanged(javax.swing.tree.TreePath path, Object newValue) {
       //if the model is correctly implemented, this will trigger a change
       //event
       treeModel.valueForPathChanged(path, newValue);
   }

   public boolean isLargeModel() {
       return layout instanceof FixedHeightLayoutCache;
   }
   

   
}